package com.example.demo.advice;

import com.example.demo.annotation.Decrypt;
import com.example.demo.config.SecretKeyConfig;
import com.example.demo.exception.EncryptRequestException;
import com.example.demo.util.Base64Util;
import com.example.demo.util.JsonUtils;
import com.example.demo.util.RSAUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ReactiveHttpInputMessage;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;

import java.io.*;
import java.util.concurrent.atomic.AtomicReference;

/**
 * Author:Bobby
 * DateTime:2019/4/9
 **/
public class DecryptHttpInputMessage implements ReactiveHttpInputMessage {

    private Logger log = LoggerFactory.getLogger(this.getClass());
    private HttpHeaders headers;
    private Flux<DataBuffer> body;


    public DecryptHttpInputMessage(ServerWebExchange serverWebExchange, SecretKeyConfig secretKeyConfig, Decrypt decrypt) throws Exception {

        String privateKey = secretKeyConfig.getPrivateKey();
        String charset = secretKeyConfig.getCharset();
        boolean showLog = secretKeyConfig.isShowLog();
        boolean timestampCheck = secretKeyConfig.isTimestampCheck();

        if (StringUtils.isEmpty(privateKey)) {
            throw new IllegalArgumentException("privateKey is null");
        }

        this.headers = serverWebExchange.getRequest().getHeaders();
        Flux<DataBuffer> content = serverWebExchange.getRequest().getBody();
        AtomicReference<String> decryptBody = new AtomicReference<>();
        content.subscribe(buffer -> {
            byte[] bytes = new byte[buffer.readableByteCount()];
            buffer.read(bytes);
            DataBufferUtils.release(buffer);
            try {
                String bodyString = new String(bytes, "utf-8");
                // 未加密内容
                if (bodyString.startsWith("{")) {
                    // 必须加密
                    if (decrypt.required()) {
                        log.error("not support unencrypted content:{}", bodyString);
                        throw new EncryptRequestException("not support unencrypted content");
                    }
                    log.info("Unencrypted without decryption:{}", bodyString);
                    decryptBody.set(bodyString);
                } else {
                    StringBuilder json = new StringBuilder();
                    bodyString = bodyString.replaceAll(" ", "+");

                    if (!StringUtils.isEmpty(bodyString)) {
                        String[] contents = bodyString.split("\\|");
                        for (String value : contents) {
                            value = new String(RSAUtil.decrypt(Base64Util.decode(value), privateKey), charset);
                            json.append(value);
                        }
                    }
                    bodyString = json.toString();
                    if (showLog) {
                        log.info("Encrypted data received：{},After decryption：{}", content, decryptBody);
                    }
                }
                buffer.write(bodyString.getBytes());
            } catch (Exception e) {
                log.error("加密异常,e:{}", e);
            }
        });
        // 开启时间戳检查
        if (timestampCheck) {
            // 容忍最小请求时间
            long toleranceTime = System.currentTimeMillis() - decrypt.timeout();
            long requestTime = JsonUtils.getNode(decryptBody.get(), "timestamp").asLong();
            // 如果请求时间小于最小容忍请求时间, 判定为超时
            if (requestTime < toleranceTime) {
                log.error("Encryption request has timed out, toleranceTime:{}, requestTime:{}, After decryption：{}", toleranceTime, requestTime, decryptBody.get());
                throw new EncryptRequestException("request timeout");
            }
        }
    }

    @Override
    public Flux<DataBuffer> getBody() {
        return body;
    }

    @Override
    public HttpHeaders getHeaders() {
        return headers;
    }
}
